//
// Created by 邹迪凯 on 2021/7/19.
//

#ifndef LWPT_ASSIGNMENT_H
#define LWPT_ASSIGNMENT_H

#include <vector>
#include <fstream>
#include <iostream>

using namespace std;

#include "DataSource.h"

const char SPLITTER = '#';

class Server;

class Worker {
public:
    string id;
    Location location;

    Worker(string id, Location location) : id(id), location(location) {}

    friend ostream &operator<<(ostream &ostream, const Worker &worker) {
        ostream << "W(" << worker.id << worker.location << ")";
        return ostream;
    }

    virtual Chunk *apply(Server &server) = 0;

    virtual ~Worker() = default;
};

class Server {
public:
    vector<Chunk *> *applications;
    vector<Chunk *> *registrations;
    vector<pair<string, string> *> assignments;
    int assignedTaskCount;

    Server() {
        applications = new vector<Chunk *>;
        registrations = new vector<Chunk *>;
        assignedTaskCount = 0;
    }

    virtual void acceptRegistration(Chunk *a) {
        registrations->push_back(a);
    }

    virtual void acceptApplication(Chunk *a) {
        applications->push_back(a);
    }

    virtual void prepareForAssignment() = 0;

    virtual void assignTask() = 0;

    virtual ~Server() = default;
};

const int ID_LENGTH = 5;

class Test {
public:
    long transmission;
    long long timeMetric;
    double travelCost;
    double assignedTaskRatio;
    vector<Worker *> workers;
    vector<Task> tasks;
    Server *server;
    DataSource *ds;

    Test(DataSource *ds) : ds(ds), transmission(0), timeMetric(0) {}

    void initWorkersAndTasks() {
        server = serverFactory();
        workers.clear();
        tasks.clear();
        transmission = 0;
        timeMetric = 0;
        travelCost = 0;
        assignedTaskRatio = 0;
        auto idSetPtr = generateRandomIds(ds->workers.size() + ds->tasks.size() + 100, ID_LENGTH, 78767575);
        auto idItr = idSetPtr->begin();
        for (int i = 0; i < ds->workers.size(); ++i) {
            workers.push_back(workerFactory(*idItr, ds->workers[i]));
            idItr++;
        }
        for (int i = 0; i < ds->tasks.size(); ++i) {
            tasks.emplace_back(*idItr, ds->tasks[i]);
            idItr++;
        }
        delete idSetPtr;
    }

    virtual Chunk *createTaskRegistration(Task &task) = 0;

    virtual Worker *workerFactory(string id, Location location) = 0;

    virtual Server *serverFactory() = 0;

    Test *start() {
        initWorkersAndTasks();
        for (auto &task : tasks) {
            Chunk *c = createTaskRegistration(task);
            measureTransmission(c);
            server->acceptRegistration(c);
        }
        for (auto worker : workers) {
            Chunk *ap = worker->apply(*server);
            measureTransmission(ap);
            server->acceptApplication(ap);
        }
        long long start = now();
        server->prepareForAssignment();
        timeMetric += now() - start;
        server->assignTask();
        return this;
    }

    virtual string name() = 0;

    virtual ~Test() {
        delete server;
        for (const auto &worker : workers) {
            delete worker;
        }
    }

    void measureTransmission(Chunk *chunk) {
        transmission += chunk->size();
    }

    virtual void result(ostream &of) {
        of << name() << ","
           << workers.size() << ","
           << tasks.size() << ","
           << transmission << ","
           << timeMetric;
    }

    Test *calculateAssignedTaskRatio() {
        assignedTaskRatio = static_cast<double> (server->assignedTaskCount) / tasks.size();
        return this;
    }

    Test *calculateTravelCost() {
        double totalTravelCost = 0;
        for (auto a:server->assignments) {
            int ti = findTask(a->first);
            int wi = findWorker(a->second);
            if (ti == -1 || wi == -1) {
//                cout << "invalid task assignment" << endl;
                continue;
            }
            auto tl = tasks[ti].location;
            auto wl = workers[wi]->location;
            totalTravelCost += earthDistance(stod(tl.lat), stod(tl.lon), stod(wl.lat), stod(wl.lon));
        }
        travelCost = totalTravelCost;
        return this;
    }

    virtual int findWorker(string id) {
        for (int i = 0; i < workers.size(); i++) {
            if (workers[i]->id == id) {
                return i;
            }
        }
        return -1;
    }

    int findTask(string id) {
        for (int i = 0; i < tasks.size(); i++) {
            if (tasks[i].id == id) {
                return i;
            }
        }
        return -1;
    }

    friend ostream &operator<<(ostream &of, Test &test) {
        test.result(of);
        return of;
    }
};


#endif //LWPT_ASSIGNMENT_H
